﻿using System;
using Glue.Internals;

namespace Glue.Converters
{
    public abstract class BaseConverter<T1,T2>:IConverter
    {
        public abstract T2 MapTowardsRight(T1 from, T2 to);
        public abstract T1 MapTowardsLeft(T2 from, T1 to);
        private readonly NullableTypeComparer nullableTypeComparer = new NullableTypeComparer();

        public object Map(object from, object to)
        {
            if (from == null && to == null)
                return null; //toObject is also not primitive (since it is null), so returning null is safe
            
            var fromType = GetFromType(from,to);

            if (fromType == typeof(T1))
            {
                try
                {
                    return MapTowardsRight(CastToT1(from), CastToT2(to));
                }
                catch 
                {
                    throw new GlueException(String.Format("The converter '{0}' cannot map the value '{1}' to a {2}-value", GetConverterName(), from, typeof(T2).Name));
                }
            }
                
            if (fromType == typeof(T2))
            {
                try
                {
                    return MapTowardsLeft(CastToT2(from), CastToT1(to));
                }
                catch
                {
                    throw new GlueException(String.Format("The converter '{0}' cannot map the value '{1}' to a {2}-value", GetConverterName(), from, typeof(T1).Name));
                }
            }

            throw new GlueException("The Converter '" + GetConverterName() + "' cannot map from type '" + fromType.Name + "'.");
        }

        private Type GetFromType(object from, object to)
        {
            if (from != null)
            {
                var theType = from.GetType();
                if (nullableTypeComparer.AreEqualOrNullableEqual(theType, typeof(T1)))
                    return typeof(T1);
                if (nullableTypeComparer.AreEqualOrNullableEqual(theType, typeof(T2)))
                    return typeof(T2);
                throw new GlueException("'" + GetConverterName() + "' received wrong type (" + theType.GetReadableName() + "). Can't resolve direction.");
                
            }
            if (to != null)
            {
                var theType = to.GetType();
                if (nullableTypeComparer.AreEqualOrNullableEqual(theType, typeof(T1)))
                    return typeof(T2);
                if (nullableTypeComparer.AreEqualOrNullableEqual(theType, typeof(T2)))
                    return typeof(T1);
                throw new GlueException("'" + GetConverterName() + "' received wrong type (" + theType.GetReadableName() + "). Can't resolve direction.");
            }

            throw new GlueException(GetConverterName() + " received null as both from-object and to-object. Can't resolve direction.");
        }

        private T1 CastToT1(object to)
        {
            if (to == null)
                return default(T1);
            
            return (T1) to;
        }

        private T2 CastToT2(object from)
        {
            if (from == null)
                return default(T2);

            return (T2)from;
        }

        protected string GetConverterName()
        {
            return GetType().Name + "<" + typeof(T1).Name + "," + typeof(T2).Name + ">";
        }

        public Type LeftType
        {
            get { return typeof(T1); }
        }

        public Type RightType
        {
            get { return typeof(T2); }
        }
    }
}
